package com.tcp2.net;


import com.tcp2.server.DxsConfig;
import com.tcp2.server.ProtoConstants;
import com.tcp2.server.ProtoMsg;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

/**
 * Author: 程行荣
 * Time: 2013-07-29 10:49
 * Copyright (C) 2013 Xiamen Yaxon Networks CO.,LTD.
 */

public class ProtoMsgDecoder extends ByteToMessageDecoder {
    private static final Logger logger = LoggerFactory.getLogger(ProtoMsgDecoder.class);

    private static final int BUF_INIT_SIZE = 32;

    private ByteBuf buf;
    private boolean begin = false;
    private byte prev = 0;

    private long M1 = 1;
    private long IA1 = 0;
    private long IC1 = 0;

    public ProtoMsgDecoder(DxsConfig dxsConfig) {
        M1 = dxsConfig.getEncryptM1();
        IA1 = dxsConfig.getEncryptIA1();
        IC1 = dxsConfig.getEncryptIC1();
    }

    @Override
    protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
        releaseBuf();
    }

    @Override
    protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        int len = in.readableBytes();
        if (len <= 0) {
            return;
        }

        for (int i = 0; i < len; i++) {
            byte b = in.readByte();
            if (b != 0x5b && !begin) {
                continue;
            }

            switch (b) {
                case 0x5b: //头标识字符
                    releaseBuf();
                    buf = Unpooled.buffer(BUF_INIT_SIZE);
                    begin = true;
                    break;
                case 0x5d: //尾标识字符
                    ProtoMsg msg = parseProtoMsg(buf);
                    if (msg != null) {
                        out.add(msg);
                    }
                    releaseBuf();
                    begin = false;
                    break;
                case 0x5a:
                case 0x5e:
                    if (prev == 0x5a || prev == 0x5e) {
                        begin = false;
                    }
                    break;
                case 0x01:
                    if (prev == 0x5a) {
                        buf.writeByte(0x5b);
                    } else if (prev == 0x5e) {
                        buf.writeByte(0x5d);
                    } else {
                        buf.writeByte(0x01);
                    }
                    break;
                case 0x02:
                    if (prev == 0x5a) {
                        buf.writeByte(0x5a);
                    } else if (prev == 0x5e) {
                        buf.writeByte(0x5e);
                    } else {
                        buf.writeByte(0x02);
                    }
                    break;
                default:
                    if (prev == 0x5a || prev == 0x5e) {
                        begin = false;
                    } else {
                        buf.writeByte(b);
                    }
                    break;
            }

            prev = b;
        }

    }

    private void releaseBuf() {
        if (buf != null) {
            buf.release();
            buf = null;
        }
    }

    private ProtoMsg parseProtoMsg(ByteBuf buf) {
        ProtoMsg msg = null;
        try {
            int bytes = buf.readableBytes();
            if (bytes < ProtoConstants.MSG_HEAD_SIZE + 2) {
                throw new Exception("协议数据包长度小于" + (ProtoConstants.MSG_HEAD_SIZE+2) + ": " + bytes);
            }

            if (!crcCheck(buf)) {
                throw new Exception("CRC校验失败");
            }

            long msgLen = buf.readUnsignedInt();
            if (msgLen != bytes + 2) {//头标识、数据头、数据体、 CRC 校验码和尾标识
                throw new Exception(String.format("协议头消息长度(%d)与实际协议数据包长度(%d)不一致", msgLen, bytes+2));
//                logger.warn("协议头消息长度({})与实际协议数据包长度({})不一致", msgLen, bytes+2);
            }

            int msgSn = buf.readInt(); //消息流水号
            int msgId = buf.readUnsignedShort(); //消息ID
            int msgGnssCenterId = buf.readInt();//下级平台接入码
            buf.skipBytes(3);//协议版本标识，不处理
            byte encryptFlag = buf.readByte();//加密标识
            int encryptKey = buf.readInt();//加密密钥

            byte[] data = new byte[buf.readableBytes()-2]; //尾部的2字节crc校验码不算
            buf.readBytes(data);
            if (encryptFlag != 0) {//数据加密，需要先解密
                decrypt(encryptKey, data);
            }

            msg = new ProtoMsg();
            msg.msgId = msgId;
            msg.msgSn = msgSn;
            msg.gnssCenterId = msgGnssCenterId;
            msg.msgData = Unpooled.wrappedBuffer(data);
        } catch (Exception e) {
            msg = null;
            logger.info("解析协议数据包失败:" + e.getMessage());
        }
        return msg;
    }

    private void decrypt(int key, byte[] data) {
//        long M1 = 1;
//        long IA1 = 0;
//        long IC1 = 0;

        long k = 1;
        if (key != 0) {
            k = (0xffffffffL & key);
        }

        for (int i = 0; i < data.length; i++) {
            byte b = data[i];
            k = (IA1 * (k % M1) + IC1) & 0xFFFFFFFFL;
            data[i] = (byte)(b ^ ((k >> 20) & 0xFF));
        }
    }

    private boolean crcCheck(ByteBuf buf) {
        buf.markReaderIndex();

        int crc = 0xffff;          // initial value
        int polynomial = 0x1021;   // 0001 0000 0010 0001  (0, 5, 12)
        int size = buf.readableBytes() - 2;
        for (int j = 0; j < size; j++) {
            byte b = buf.readByte();
            for (int i = 0; i < 8; i++) {
                boolean bit = ((b   >> (7-i) & 1) == 1);
                boolean c15 = ((crc >> 15    & 1) == 1);
                crc <<= 1;
                if (c15 ^ bit) crc ^= polynomial;
            }
        }
        crc &= 0xffff;

        int crc0 = buf.readUnsignedShort();
        if (crc == crc0) {
            buf.resetReaderIndex();
            return true;
        } else {
            return false;
        }
    }
}
